/* 
 * Advanced Foundation Classes
 * Copyright (C) 2000/2004  Fabio Rotondo - Andrea Galimberti
 *  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include "tclist.h"

static EGTCListData * afc_easygui_internal_cb_list_data_new ( EasyGUI * egui, char * cols, char * alignment );
static int afc_easygui_internal_cb_list_data_delete ( EGTCListData * cldata );
static int afc_easygui_internal_tclist_get_template ( DynamicClass * dyn );
static int afc_easygui_internal_cb_tclist ( DynamicClass * dyn );

// METHODS
static int afc_easygui_tclist_method_add ( DynamicClass * dyn );
static int afc_easygui_tclist_method_set_titles ( DynamicClass * dyn );
static int afc_easygui_tclist_method_clear ( DynamicClass * dc );

static int afc_easygui_tclist_method_get_attr ( DynamicClass * dc );
static int afc_easygui_tclist_method_set_attr ( DynamicClass * dc );

// EVENTS
static void afc_easygui_tclist_event_activated ( GtkTreeView * tv, GtkTreePath * tp, GtkTreeViewColumn * col );
static void afc_easygui_tclist_event_changed ( GtkTreeView * tv, GtkTreePath * tp, GtkTreeViewColumn * col );
static gboolean afc_easygui_tclist_event_selected ( GtkTreeSelection * sel, GtkTreeModel * model, GtkTreePath * path, gboolean curr_selected, gpointer data );

// INTERNAL FUNCTIONS
static void afc_easygui_tclist_internal_call_callback ( DynamicClass * dc, EasyGUITCListSignalFunc sig, GtkTreePath * tp );
static void afc_easygui_tclist_internal_fill_cldata ( DynamicClass * dc, GtkTreePath * tp, EGTCListData * * cld, void * * * inf );
static void afc_easygui_internal_tclist_set_selmode ( EasyGUI * egui, EGTCListData * cldata, char * selmode );

static const char class_name[] = "Widget TCList";

/*
@node afc_easygui_internal_tclist_new_instance

@endnode
*/
DynamicClass * afc_easygui_internal_tclist_new_instance ()
{
	DynamicClass * dyn = NULL;

	if ( ( dyn = afc_dynamic_class_new () ) == NULL ) {
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "dynamic class: entry" );
		return ( NULL );
	}

	afc_dynamic_class_add_method ( dyn, "get_template", NULL,  afc_easygui_internal_tclist_get_template );
	afc_dynamic_class_add_method ( dyn, "open_callback", NULL,  afc_easygui_internal_cb_tclist );
	afc_dynamic_class_add_method ( dyn, "enabled",	NULL, 	afc_easygui_widget_method_enabled );

	afc_dynamic_class_add_method ( dyn, "add", NULL,  afc_easygui_tclist_method_add );
	afc_dynamic_class_add_method ( dyn, "set_titles", NULL,  afc_easygui_tclist_method_set_titles );
	afc_dynamic_class_add_method ( dyn, "clear", NULL,  	  afc_easygui_tclist_method_clear );

	afc_dynamic_class_add_method ( dyn, "get_attr", NULL,  afc_easygui_tclist_method_get_attr );
	afc_dynamic_class_add_method ( dyn, "set_attr", NULL,  afc_easygui_tclist_method_set_attr );
	// afc_dynamic_class_add_method ( dyn, "set_list", NULL,    afc_easygui_tclist_method_set_list );
	return ( dyn );
}

/*
@node afc_easygui_internal_tclist_del_instance

@endnode
*/
int afc_easygui_internal_tclist_del_instance ( DynamicClass * dyn )
{
	EGTCListData * tcdata = NULL;

	if ( dyn == NULL ) return ( AFC_LOG_FAST ( AFC_ERR_NULL_POINTER ) );

	if ( dyn->vars != NULL ) {
		AFC_TCLIST_GET_CLDATA ( dyn, tcdata );

		if ( tcdata ) afc_easygui_internal_cb_list_data_delete ( tcdata );
	}

	return ( afc_easygui_widget_standard_free ( dyn ) );
}

/*
@node afc_easygui_internal_tclist_get_template
	
@endnode

	COLUMNS		- Eg. "Col 1|Col 2|Column 3"
	SELMODE		- SINGLE|BROWSE|MULTI
	NOTITLES/S	- If set, the TCList will have no titles

	ACTIVATED	- Row Double Clicked event
	CHANGED		- Row changed its status event
	SELECTED	- Row selected event (fired BEFORE the row actually changed state)
*/
static int afc_easygui_internal_tclist_get_template ( DynamicClass * dyn )
{
	dyn->result = "COLUMNS/A SELMODE NOTITLES/S ALIGNMENT ACTIVATED CHANGED SELECTED";
	return ( AFC_ERR_NO_ERROR );
}

/*
@node afc_easygui_internal_cb_tclist

@endnode
*/
static int afc_easygui_internal_cb_tclist ( DynamicClass * dyn )
{
	EasyGUI * egui = dyn->info;
	EasyGUIContext * context = egui->curr_context;
	EGTCListData * cldata;
	char * columns, * selmode, * alignment;
	char * activated, * changed, * selected;
	short  notitles;
	int afc_res = AFC_ERR_NO_ERROR;
	DynamicClass * dc;

	if ( context->curr_container == NULL ) return ( AFC_EASYGUI_ERR_NO_CONTAINER );

	afc_easygui_widget_parse_default_params ( egui );

	columns   = afc_cmd_parser_arg_get_by_name ( egui->parser, "COLUMNS" );
	selmode   = afc_cmd_parser_arg_get_by_name ( egui->parser, "SELMODE" );
	alignment = afc_cmd_parser_arg_get_by_name ( egui->parser, "ALIGNMENT" ); 
	activated = afc_cmd_parser_arg_get_by_name ( egui->parser, "ACTIVATED" );
	changed   = afc_cmd_parser_arg_get_by_name ( egui->parser, "CHANGED" );
	selected  = afc_cmd_parser_arg_get_by_name ( egui->parser, "SELECTED" );

	notitles = ( short ) ( int ) afc_cmd_parser_arg_get_by_name ( egui->parser, "NOTITLES" );

	if ( ( dc = afc_dynamic_class_master_new_instance ( egui->commands, "tclist" ) ) == NULL )
		return ( AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "dynclass instance" ) );

	WIDGET_SET_CONTEXT ( dc, context );

	if ( ( cldata = afc_easygui_internal_cb_list_data_new ( egui, columns, alignment ) ) == NULL )
		return ( AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "cldata" ) );

	if ( ( afc_res = afc_easygui_internal_add_widget ( context, cldata->scrollwin, dc ) ) != AFC_ERR_NO_ERROR ) return ( afc_res );

	if ( notitles ) gtk_tree_view_set_headers_visible ( GTK_TREE_VIEW ( cldata->view ), FALSE );

	afc_easygui_internal_tclist_set_selmode ( egui, cldata, selmode );

	// Store the cldata struct inside a DynClass data
	afc_dynamic_class_set_var ( dc, AFC_DYNAMIC_CLASS_VAR_KIND_POINTER, "cldata", cldata );

	// Sets an internal array value to know the Dynamic Class
	AFC_TCLIST_STORE_DYNCLASS ( cldata->view, dc );

	// ------------------------------------------------------------------------------------------------
	// Sets the default signal handlers
	// ------------------------------------------------------------------------------------------------

	// Row double click
	if ( ( afc_res = afc_easygui_widget_connect_signal ( dc, cldata->view, "row-activated", activated, NULL, (GtkSignalFunc) afc_easygui_tclist_event_activated ) ) != AFC_ERR_NO_ERROR )
		return ( afc_res );

	// Row selected / unselected
	if ( ( afc_res = afc_easygui_widget_connect_signal ( dc, (GtkWidget * )cldata->selection, "changed", changed, NULL, (GtkSignalFunc) afc_easygui_tclist_event_changed ) ) != AFC_ERR_NO_ERROR )
		return ( afc_res );

	if ( selected )
	{
		if ( ( afc_res = afc_easygui_widget_connect_signal ( dc, (GtkWidget * ) cldata->selection, "selected", selected, NULL, NULL  ) ) != AFC_ERR_NO_ERROR )
			return ( afc_res );

		gtk_tree_selection_set_select_function ( GTK_TREE_SELECTION ( cldata->selection ), (GtkTreeSelectionFunc) afc_easygui_tclist_event_selected, dc, NULL );	
	}
	

	return ( afc_res );
}

static EGTCListData * afc_easygui_internal_cb_list_data_new ( EasyGUI * egui, char * cols, char * alignment )
{
	EGTCListData * cldata = afc_malloc ( sizeof ( EGTCListData ) );
	GType * types;
	GtkWidget * column;
	char * s, * al;
	int t;
	StringNode * aligns;
	GtkCellRenderer * cell;
	gfloat align;

	if ( cldata == NULL ) 
	{
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "cldata" );
		return ( NULL );
	}

	if ( ( aligns = afc_stringnode_new () ) == NULL )
	{
		afc_free ( cldata );
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "aligns" );
		return ( NULL );
	}

	if ( cols == NULL ) cols = "1";	// Fallback to avoid errors

	afc_stringnode_split ( egui->int_data->snode, cols, egui->int_data->delimiters );

	cldata->columns = afc_stringnode_len ( egui->int_data->snode ) +1;

	// Prepare the types array (used by list_store_newv() )
	if ( ( types = afc_malloc ( ( sizeof ( GType ) ) * ( cldata->columns + 1 ) ) ) == NULL )
	{
		afc_easygui_internal_cb_list_data_delete ( cldata );
		afc_stringnode_delete ( aligns );
		return ( NULL );
	}

	if ( ( cldata->col_data = afc_malloc ( sizeof ( char * ) * ( cldata->columns ) ) ) == NULL )
	{
		AFC_LOG_FAST_INFO ( AFC_ERR_NO_MEMORY, "col_data" );
		afc_easygui_internal_cb_list_data_delete ( cldata );
		afc_stringnode_delete ( aligns );
		return ( NULL );
	}

	// fill the types with G_TYPE_STRING
	for ( t = 0; t < cldata->columns; t ++ ) types [ t ] = G_TYPE_STRING;
	types [ cldata->columns-1 ] = G_TYPE_POINTER;	// The last column is hidden and holds row specific data

	// Create the ScrollWindow (the main container)
	cldata->scrollwin = gtk_scrolled_window_new ( NULL, NULL );
	gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW ( cldata->scrollwin ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );

	// Create the store model
	cldata->model = gtk_list_store_newv ( cldata->columns, types );

	// We do not need the types array anymore now
	afc_free ( types );
	
	// Create the view
	cldata->view = gtk_tree_view_new_with_model ( GTK_TREE_MODEL ( cldata->model ) );

	// Add View to the Scrolled Window
	gtk_container_add ( GTK_CONTAINER ( cldata->scrollwin ), cldata->view );

	// Unref the model, since the tree view adds a reference by itself
	g_object_unref ( G_OBJECT ( cldata->model ) );

	// Get the GtkSelection from the View
	cldata->selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( cldata->view ) );

	// Remove the last ("info") field from column count
	cldata->columns -= 1;

	// Parses the aligns string
	if ( alignment )
		afc_stringnode_split ( aligns, alignment, egui->int_data->delimiters );

	// Creates the columns
	for ( t = 0; t < cldata->columns; t ++ )
	{
		cell = gtk_cell_renderer_text_new ();
		column = ( GtkWidget * ) gtk_tree_view_column_new_with_attributes ( "", cell, "text", t, NULL );
		gtk_tree_view_append_column ( GTK_TREE_VIEW ( cldata->view ), GTK_TREE_VIEW_COLUMN ( column ) );
		gtk_tree_view_column_set_resizable ( GTK_TREE_VIEW_COLUMN ( column ), TRUE );

		if ( ( s = afc_stringnode_item ( egui->int_data->snode, t ) ) != NULL ) gtk_tree_view_column_set_title ( GTK_TREE_VIEW_COLUMN ( column ), s );

		
		if ( ( al = afc_stringnode_item ( aligns, t ) ) != NULL )
		{
			align = 0;
			if ( strcasecmp ( al, "CENTER" ) == 0 )    align = 0.5;
			else if ( strcasecmp (al, "RIGHT" ) == 0 ) align=1.0;
			g_object_set ( G_OBJECT ( cell ), "xalign", align, NULL, NULL );
		}
	}

	// Deletes the align splitter class
	afc_stringnode_delete ( aligns );
	

	return ( cldata );
}

static int afc_easygui_internal_cb_list_data_delete ( EGTCListData * cldata )
{
	if ( cldata == NULL ) return ( AFC_LOG_FAST ( AFC_ERR_NULL_POINTER ) );

	if ( cldata->col_data ) afc_free ( cldata->col_data );

	afc_free ( cldata );

	return ( AFC_ERR_NO_ERROR );
}

// ------------------------------------------------------------------------------------------------------------------------
// TCList Methods
// ------------------------------------------------------------------------------------------------------------------------

static int afc_easygui_tclist_method_add ( DynamicClass * dyn )
{
	char * s;
	GtkTreeIter iter;
	EasyGUIWidgetData * ewidget;
	EGTCListData * cldata;
	int t = 0;
	void * info;

	WIDGET_GET_EWIDGET ( dyn, ewidget );
	AFC_TCLIST_GET_CLDATA ( dyn, cldata );	

	gtk_list_store_append ( GTK_LIST_STORE ( cldata->model ), &iter );

	// Get the last element (the info field)
	afc_array_master_last ( dyn->args );
	info = afc_array_master_del ( dyn->args );
	afc_array_master_del ( dyn->args );

	s = afc_array_master_first ( dyn->args );
	while ( ( s != NULL )  && ( s != AFC_EASYGUI_ARG_END ) )
	{
		gtk_list_store_set ( GTK_LIST_STORE ( cldata->model ), &iter, t, s, -1 );
		t++;

		if ( t >= cldata->columns ) break;	// TODO: show a warning message

		s = afc_array_master_next ( dyn->args );
	}

	gtk_list_store_set ( GTK_LIST_STORE ( cldata->model ), &iter, cldata->columns, info, -1 );

	return ( AFC_ERR_NO_ERROR );
}

static int afc_easygui_tclist_method_set_titles ( DynamicClass * dyn )
{
	EasyGUIWidgetData * ewidget;
	EGTCListData * cldata;
	GtkTreeViewColumn * col;
	int t = 0;
	char * s;

	WIDGET_GET_EWIDGET ( dyn, ewidget );
	AFC_TCLIST_GET_CLDATA ( dyn, cldata );	

	s = afc_array_master_first ( dyn->args );
	while ( ( s != NULL ) && ( s != AFC_EASYGUI_ARG_END ) )
	{
		col = gtk_tree_view_get_column ( GTK_TREE_VIEW ( cldata->view ), t );
		gtk_tree_view_column_set_title ( col, s );

		t++;
		s = afc_array_master_next ( dyn->args );
	}
	
	
	return ( AFC_ERR_NO_ERROR );
}

// Gets an attr from the widget
static int afc_easygui_tclist_method_get_attr ( DynamicClass * dc )
{
	char * * buffer;
	int	 attr;
	EGTCListData * cldata;
	EasyGUIWidgetData * ewidget;

	WIDGET_GET_EWIDGET ( dc, ewidget );
	AFC_TCLIST_GET_CLDATA ( dc, cldata );	

	attr 	= ( int ) afc_array_master_first ( dc->args );
	buffer 	= ( char * * ) afc_array_master_next ( dc->args );

	switch ( attr )
	{
		case AFC_EASYGUI_WIDGET_ATTR_VALUE:
			dc->result = ( void * ) gtk_entry_get_text ( GTK_ENTRY ( ewidget->gtk_handle ) );		
			*buffer = ( char * ) dc->result;
			break;

		case AFC_EASYGUI_WIDGET_ATTR_NAME:
			dc->result = ( void * ) gtk_widget_get_name ( GTK_WIDGET ( ewidget->gtk_handle ) );
			*buffer = ( char * ) dc->result;
			break;

		default:
			AFC_LOG_FAST ( AFC_ERR_UNSUPPORTED_TAG );
	}

	return ( AFC_ERR_NO_ERROR );
}

// Sets an attr value to the widget
static int afc_easygui_tclist_method_set_attr ( DynamicClass * dc )
{
	char * buffer;
	int	 attr;
	EGTCListData * cldata;
	EasyGUIWidgetData * ewidget;

	WIDGET_GET_EWIDGET ( dc, ewidget );
	AFC_TCLIST_GET_CLDATA ( dc, cldata );	

	attr 	= ( int ) afc_array_master_first ( dc->args );
	buffer 	= ( char * ) afc_array_master_next ( dc->args );

	switch ( attr )
	{
		case AFC_EASYGUI_WIDGET_ATTR_VISIBLE:
			g_object_set ( G_OBJECT ( ewidget->gtk_handle ), "visible", ( gboolean ) ( int ) buffer, NULL, NULL );
			break;

		case AFC_EASYGUI_WIDGET_ATTR_ACTIVE:
			g_object_set ( G_OBJECT ( ewidget->gtk_handle ), "sensitive", ( gboolean ) ( int ) buffer, NULL, NULL );
			break;

		case AFC_EASYGUI_WIDGET_ATTR_FOCUS:
			gtk_widget_grab_focus ( GTK_WIDGET ( ewidget->gtk_handle ) );
			break;

		case AFC_EASYGUI_WIDGET_ATTR_NAME:
			gtk_widget_set_name ( GTK_WIDGET ( ewidget->gtk_handle ), ( const char * ) buffer );
			break;

		case AFC_EASYGUI_TCLIST_ATTR_SHOW_TITLES:
			gtk_tree_view_set_headers_visible ( GTK_TREE_VIEW ( cldata->view ), ( gboolean ) ( int ) buffer );
			break;

		default:
			fprintf ( stderr, "TCLIST: unsupported attr\n" );
	}

	return ( AFC_ERR_NO_ERROR );
}

static int afc_easygui_tclist_method_clear ( DynamicClass * dc )
{
	EGTCListData * cldata;

	AFC_TCLIST_GET_CLDATA ( dc, cldata );	

	gtk_list_store_clear ( GTK_LIST_STORE ( cldata->model ) );

	return ( AFC_ERR_NO_ERROR );
}

// ------------------------------------------------------------------------------------------------------------------------
// EVENT FUNCTIONS
// ------------------------------------------------------------------------------------------------------------------------
static void afc_easygui_tclist_event_activated ( GtkTreeView * tv, GtkTreePath * tp, GtkTreeViewColumn * col )
{
	DynamicClass * dc;
	EasyGUITCListSignalFunc sig;

	// Gets the Dynamic Class from the "hidden" store
	AFC_TCLIST_GET_DYNCLASS ( tv, dc );
	WIDGET_GET_CALLBACK_CAST (dc,"row-activated",sig,EasyGUITCListSignalFunc);

	// If we get this far, we can call the callback
	afc_easygui_tclist_internal_call_callback ( dc, sig, tp );
}

static void afc_easygui_tclist_event_changed ( GtkTreeView * tv, GtkTreePath * tp, GtkTreeViewColumn * col )
{
	DynamicClass * dc;
	EasyGUITCListSignalFunc sig;
	
	// Gets the Dynamic Class from the "hidden" store
	AFC_TCLIST_GET_DYNCLASS ( tv, dc );
	WIDGET_GET_CALLBACK_CAST (dc,"changed",sig,EasyGUITCListSignalFunc);

	// If we get this far, we can call the callback
	afc_easygui_tclist_internal_call_callback ( dc, sig, tp );
}

// If the function return TRUE, the processing for selection / deselection will occur
static gboolean afc_easygui_tclist_event_selected ( GtkTreeSelection * sel, GtkTreeModel * model, GtkTreePath * tp, gboolean curr_selected, gpointer data )
{
	DynamicClass * dc = data;
	EasyGUITCListSelectSignalFunc sig;
	void * * info;
	EGTCListData * cldata;

	// If no function has been defined, we defaults to the standard way of working
	if ( ( sig = (EasyGUITCListSelectSignalFunc) afc_dictionary_get ( dc->private_vars, "selected" ) ) == NULL ) return ( TRUE );

	afc_easygui_tclist_internal_fill_cldata ( dc, tp, &cldata, &info );

	return ( sig ( dc, WIDGET_GET_CONTEXT ( dc ), curr_selected, cldata->col_data, info ) );
}

// INTERNALS
static void afc_easygui_tclist_internal_fill_cldata ( DynamicClass * dc, GtkTreePath * tp, EGTCListData * * cld, void * * * inf )
{
	int t;
	void * * info;
	char * * str;
	EGTCListData * cldata;
        GtkTreeIter iter;

	cldata = afc_dynamic_class_get_var ( dc, "cldata" );

	*cld = cldata;

        gtk_tree_model_get_iter ( GTK_TREE_MODEL ( cldata->model ), &iter,  tp );

	for ( t = 0; t < cldata->columns; t ++ )
	{
        	gtk_tree_model_get ( GTK_TREE_MODEL ( cldata->model ), &iter, t, &str, -1 );

		cldata->col_data[t] = ( char * ) str;
	}

	gtk_tree_model_get ( GTK_TREE_MODEL ( cldata->model ), &iter, cldata->columns, &info, -1 );
	*inf = info;
}

static void afc_easygui_tclist_internal_call_callback ( DynamicClass * dc, EasyGUITCListSignalFunc sig, GtkTreePath * tp )
{
	void * * info;
	EGTCListData * cldata;

	afc_easygui_tclist_internal_fill_cldata ( dc, tp, &cldata, &info );

	sig ( dc, WIDGET_GET_CONTEXT ( dc ), cldata->col_data, info );
}

static void afc_easygui_internal_tclist_set_selmode ( EasyGUI * egui, EGTCListData * cldata, char * selmode )
{
	int mode;

	if ( selmode == NULL ) return;

	if ( strcasecmp ( selmode, "BROWSE" ) == 0 ) 		mode = GTK_SELECTION_BROWSE;
	else if ( strcasecmp ( selmode, "MULTI" ) == 0 ) 	mode = GTK_SELECTION_MULTIPLE;
	else mode = GTK_SELECTION_SINGLE;

 	gtk_tree_selection_set_mode ( GTK_TREE_SELECTION ( cldata->selection ), mode );
}

